{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tap dependent impedance of transformers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With an adjustment of a tap position of a transformer, the number of windings is adjusted, which influences the impedence. The user can provide **trafo_characteristic_table** of the dependance of the variables **voltage ratio**, **angle in degree**, **vk_percent** and **vkr_percent** on the tap position. A helper function in pandapower control module converts the provided points in a characteristic object with quadratic spline interpolation (or a user-defined characteristic object with a custom interpolation approach) and writes it in the **trafo_characteristic_spline** table. Finally, a diagnostic function checks for inconsistensies and provides information to the user."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from pandapower.create import (\n",
    "    create_empty_network,\n",
    "    create_bus,\n",
    "    create_load,\n",
    "    create_transformer,\n",
    "    create_transformer3w_from_parameters,\n",
    "    create_ext_grid,\n",
    "    create_line_from_parameters,\n",
    ")\n",
    "from pandapower.run import runpp\n",
    "from pandapower.control import SplineCharacteristic, plot_characteristic, TapDependentImpedance, create_trafo_characteristic_object, trafo_characteristic_table_diagnostic\n",
    "from pandapower.file_io import from_json_string, to_json\n",
    "\n",
    "import warnings\n",
    "warnings.simplefilter(action='ignore', category=FutureWarning)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def add_trafo_connection(net, hv_bus, element_type=\"trafo\"):\n",
    "    cb = create_bus(net, vn_kv=0.4)\n",
    "    create_load(net, cb, 0.2, 0.05)\n",
    "    \n",
    "    if element_type==\"trafo3w\":\n",
    "        cbm = create_bus(net, vn_kv=0.9)\n",
    "        create_load(net, cbm, 0.1, 0.03)\n",
    "        create_transformer3w_from_parameters(net, hv_bus=hv_bus, mv_bus=cbm, lv_bus=cb,\n",
    "                                                vn_hv_kv=20., vn_mv_kv=0.9, vn_lv_kv=0.45, sn_hv_mva=0.6, \n",
    "                                                sn_mv_mva=0.5, sn_lv_mva=0.4, vk_hv_percent=1., \n",
    "                                                vk_mv_percent=1., vk_lv_percent=1., vkr_hv_percent=0.3, \n",
    "                                                vkr_mv_percent=0.3, vkr_lv_percent=0.3, pfe_kw=0.2, \n",
    "                                                i0_percent=0.3, tap_neutral=0., tap_pos=2, tap_changer_type=\"Ratio\",\n",
    "                                                tap_step_percent=1., tap_min=-2, tap_max=2)\n",
    "    else:\n",
    "        create_transformer(net, hv_bus=hv_bus, lv_bus=cb, std_type=\"0.25 MVA 20/0.4 kV\", tap_pos=2)\n",
    "\n",
    "def create_net():\n",
    "    net = create_empty_network()\n",
    "    vn_kv = 20\n",
    "    b1 = create_bus(net, vn_kv=vn_kv)\n",
    "    create_ext_grid(net, b1, vm_pu=1.01)\n",
    "    b2 = create_bus(net, vn_kv=vn_kv)\n",
    "    l1 = create_line_from_parameters(net, b1, b2, 12.2, r_ohm_per_km=0.08, x_ohm_per_km=0.12,\n",
    "                                        c_nf_per_km=300, max_i_ka=.2, df=.8)\n",
    "    for i in range(2):\n",
    "        add_trafo_connection(net, b2)\n",
    "        net.trafo.loc[i, \"id_characteristic_table\"] = i\n",
    "        net.trafo.loc[i,\"tap_dependency_table\"] = True\n",
    "\n",
    "    # Adding the characteristics data for two- and three-winding transformers into the trafo_characteristic_table\n",
    "    net['trafo_characteristic_table'] = pd.DataFrame(\n",
    "    columns=['id_characteristic', 'step', 'voltage_ratio', 'angle_deg', 'vk_percent',\n",
    "             'vkr_percent', 'vkr_hv_percent', 'vkr_mv_percent',\n",
    "             'vkr_lv_percent', 'vk_hv_percent', 'vk_mv_percent',\n",
    "             'vk_lv_percent'])\n",
    "    trafo_data = {\n",
    "        'id_characteristic': [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],\n",
    "        'step': [-2, -1, 0, 1, 2, -2, -1, 0, 1, 2],\n",
    "        'voltage_ratio': [0.95, 0.97, 1.0, 1.03, 1.05, 0.95, 0.97, 1.0, 1.03, 1.05],\n",
    "        'angle_deg': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n",
    "        'vk_percent': [5, 5.2, 6, 6.8, 7, 5, 5.2, 6, 6.8, 7],\n",
    "        'vkr_percent': [1.3, 1.4, 1.44, 1.5, 1.6, 1.3, 1.4, 1.44, 1.5, 1.6],\n",
    "        'vkr_hv_percent': np.nan,\n",
    "        'vkr_mv_percent': np.nan,\n",
    "        'vkr_lv_percent': np.nan,\n",
    "        'vk_hv_percent': np.nan,\n",
    "        'vk_mv_percent': np.nan,\n",
    "        'vk_lv_percent': np.nan\n",
    "    }\n",
    "\n",
    "    net['trafo_characteristic_table'] = pd.DataFrame(trafo_data)\n",
    "\n",
    "    trafo3w_data = {\n",
    "        'id_characteristic': [2, 2, 2, 2, 2, 3, 3, 3, 3, 3],\n",
    "        'step': [-2, -1, 0, 1, 2, -2, -1, 0, 1, 2],\n",
    "        'voltage_ratio': [0.95, 0.97, 1.0, 1.03, 1.05, 0.95, 0.97, 1.0, 1.03, 1.05],\n",
    "        'angle_deg': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n",
    "        'vk_percent': np.nan,\n",
    "        'vkr_percent': np.nan,\n",
    "        'vkr_hv_percent': [0.27, 0.28, 0.3, 0.32, 0.33, 0.27, 0.28, 0.3, 0.32, 0.33],\n",
    "        'vkr_mv_percent': [0.27, 0.28, 0.3, 0.32, 0.33, 0.27, 0.28, 0.3, 0.32, 0.33],\n",
    "        'vkr_lv_percent': [0.27, 0.28, 0.3, 0.32, 0.33, 0.27, 0.28, 0.3, 0.32, 0.33],\n",
    "        'vk_hv_percent': [0.85, 0.9, 1, 1.1, 1.15, 0.85, 0.9, 1, 1.1, 1.15],\n",
    "        'vk_mv_percent': [0.85, 0.9, 1, 1.1, 1.15, 0.85, 0.9, 1, 1.1, 1.15],\n",
    "        'vk_lv_percent': [0.85, 0.9, 1, 1.1, 1.15, 0.85, 0.9, 1, 1.1, 1.15],\n",
    "    }\n",
    "    data_frame = pd.DataFrame(trafo3w_data)\n",
    "    net['trafo_characteristic_table'] = pd.concat([net['trafo_characteristic_table'], data_frame], sort=False)\n",
    "    return net"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net = create_net()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can create the characteristics by generating characteristic objects and assigning them to the respective columns in the **trafo_characteristic_spline table**. To create these characteristics, a convenience function can be used to initialize and configure the characteristic objects for **voltage ratio**, **angle in degree**, **vk_percent** and **vkr_percent**. This function creates the characteristics for all transfomers within the specified network. It is essential that the input parameters are provided in the correct order to ensure proper functionality."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "create_trafo_characteristic_object(net)\n",
    "net.trafo.loc[1,\"id_characteristic_table\"] = np.nan\n",
    "net.trafo.loc[1,\"tap_dependency_table\"] = False\n",
    "net.trafo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The characteristic set in the convenience function is the SplineCharacteristic object that relies on quadratic spline interpolation with interp1d from SciPy. Let us check the characteristic:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xlabel = 'Tap position \"tap_pos\"'\n",
    "plot_characteristic(net[\"trafo_characteristic_spline\"][\"vk_percent_characteristic\"].loc[0], -2, 2,\n",
    "                               xlabel=xlabel, ylabel='Value of \"vk_percent\"')\n",
    "plot_characteristic(net[\"trafo_characteristic_spline\"][\"vkr_percent_characteristic\"].loc[0], -2, 2,\n",
    "                               xlabel=xlabel, ylabel='Value of \"vkr_percent\"')\n",
    "runpp(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let us verify that the adjusted values were considered in the calculation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net.res_bus.loc[[2,3]]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that the voltage values are different, even though the connections are identical. Now let us compare the computation time:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net2 = create_net()\n",
    "net2.trafo[\"id_characteristic_table\"] = np.nan\n",
    "net2.trafo[\"tap_dependency_table\"] = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%timeit runpp(net2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%timeit runpp(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The calculation with tap dependent impedance is somewhat slower."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, let us compare the performance of the controller for reference"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net3 = create_net()\n",
    "net3.trafo.loc[1,\"id_characteristic_table\"] = np.nan\n",
    "net3.trafo.loc[1,\"tap_dependency_table\"] = False\n",
    "SplineCharacteristic(net3, [-2, -1, 0, 1, 2], [5, 5.2, 6, 6.8, 7])\n",
    "SplineCharacteristic(net3, [-2, -1, 0, 1, 2], [1.3, 1.4, 1.44, 1.5, 1.6])\n",
    "TapDependentImpedance(net3, [0], 0, output_variable=\"vk_percent\")\n",
    "TapDependentImpedance(net3, [0], 1, output_variable=\"vkr_percent\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%timeit runpp(net3, run_control=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The computational time is substantially higher!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The results from using the characteristics in pandapower directly and via controllers are identical:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net3.res_bus.loc[[2,3]]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert np.allclose(net.res_bus.vm_pu, net3.res_bus.vm_pu, atol=1e-6, rtol=0)\n",
    "assert np.allclose(net.res_bus.va_degree, net3.res_bus.va_degree, atol=1e-6, rtol=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3-Winding Transformers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net4 = create_net()\n",
    "net5 = create_net()\n",
    "for i in range(2):\n",
    "    add_trafo_connection(net4, net4.trafo.at[0, 'hv_bus'], \"trafo3w\")\n",
    "    add_trafo_connection(net5, net5.trafo.at[0, 'hv_bus'], \"trafo3w\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net4.trafo3w.loc[0,\"id_characteristic_table\"] = 2\n",
    "net4.trafo3w.loc[0,\"tap_dependency_table\"] = True\n",
    "create_trafo_characteristic_object(net4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "SplineCharacteristic(net5, [-2, -1, 0, 1, 2], [0.85, 0.9, 1, 1.1, 1.15])\n",
    "SplineCharacteristic(net5, [-2, -1, 0, 1, 2], [0.27, 0.28, 0.3, 0.32, 0.33])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "TapDependentImpedance(net5, [0], 0, output_variable=\"vk_hv_percent\",  element=\"trafo3w\")\n",
    "TapDependentImpedance(net5, [0], 1, output_variable=\"vkr_hv_percent\", element=\"trafo3w\")\n",
    "TapDependentImpedance(net5, [0], 0, output_variable=\"vk_mv_percent\",  element=\"trafo3w\")\n",
    "TapDependentImpedance(net5, [0], 1, output_variable=\"vkr_mv_percent\", element=\"trafo3w\")\n",
    "TapDependentImpedance(net5, [0], 0, output_variable=\"vk_lv_percent\",  element=\"trafo3w\")\n",
    "TapDependentImpedance(net5, [0], 1, output_variable=\"vkr_lv_percent\", element=\"trafo3w\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%timeit runpp(net4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%timeit runpp(net5, run_control=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert np.allclose(net4.res_bus.vm_pu, net5.res_bus.vm_pu, atol=1e-6, rtol=0)\n",
    "assert np.allclose(net4.res_bus.va_degree, net5.res_bus.va_degree, atol=1e-6, rtol=0)\n",
    "pd.merge(net4.res_bus[[\"vm_pu\", \"va_degree\"]], net5.res_bus[[\"vm_pu\", \"va_degree\"]], \n",
    "         left_index=True, right_index=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Also in the case of the 3-winding transformers, the results for direct calculation in pandapower and in controllers match."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Undefined characteristics"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If some transformers have characteristics that do not define all columns, the missing characteristics are ignored by default. However, if **tap_dependency_table** is set to True, one or more characteristics are missing, a warning is issued, followed by an error during the load flow calculation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net6 = create_net()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "net6.trafo.loc[1,\"tap_dependency_table\"] = False\n",
    "net6[\"trafo_characteristic_table\"].loc[net[\"trafo_characteristic_table\"][\"id_characteristic\"]==1, \"vk_percent\"] = np.nan\n",
    "create_trafo_characteristic_object(net6)\n",
    "net6.trafo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%timeit runpp(net6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#this would raise a warning:\n",
    "#net6.trafo.loc[1,\"tap_dependency_table\"] = True\n",
    "#trafo_characteristic_table_diagnostic(net6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#this would raise an error:\n",
    "#runpp(net6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Creating transformers with characteristics"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The functions to create transformers have been updated to include the parameters **tap_dependency_table** and **id_characteristic_table** in trasformer which help to create the characteristics object and add the references to the **id_characteristic_spline** in trasformer using **create_trafo_characteristic_object** function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net_create = create_net()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net_create.trafo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "create_transformer(net_create, hv_bus=net_create.trafo.at[0, 'hv_bus'],\n",
    "                      lv_bus=net_create.trafo.at[0, 'lv_bus'], std_type=\"0.25 MVA 20/0.4 kV\", tap_pos=2, \n",
    "                      tap_dependency_table=True, id_characteristic_table = 0)\n",
    "create_trafo_characteristic_object(net_create)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net_create.trafo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "runpp(net_create)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# File I/O"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The file I/O for characteristics is now implemented via adding the characteristic table:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net2 = from_json_string(to_json(net))\n",
    "create_trafo_characteristic_object(net2) # need to recreate the object becuase it is binary\n",
    "\n",
    "net2[\"trafo_characteristic_spline\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De-serialization is working:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_characteristic(net2[\"trafo_characteristic_spline\"][\"vk_percent_characteristic\"].loc[0], -2, 2,\n",
    "                               xlabel=xlabel, ylabel='Value of \"vk_percent\"')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Diagnostic"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The diagnostic function for transformer characteristics checks the following:\n",
    " \n",
    "* Are there transformer characteristic table and tap-dependent characteristics?\n",
    "* Are any characteristics missing?\n",
    "* Are there **tap_dependency_table**, **tap_changer_type** and **id_characteristic_table** missing in net.trafo or net.trafo3w?\n",
    "* Check if all relevant columns for creating transformer characteristics are populated in the **trafo_characteristic_table**\n",
    "* Check the data types of **tap_dependency_table** and **id_characteristic_table**\n",
    "* Are there any missing **id_characteristic_table** values in the **trafo_characteristic_table**?\n",
    "\n",
    "The results are displayed to the user with the help of warning statements."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net.trafo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "trafo_characteristic_table_diagnostic(net)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "trafo_characteristic_table_diagnostic(net6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net_diagnostics = net\n",
    "net_diagnostics.trafo.loc[0, \"id_characteristic_table\"] = None\n",
    "trafo_characteristic_table_diagnostic(net_diagnostics)\n",
    "#UserWarning: trafo: found 1 transformer(s) with not both tap_dependency_table and id_characteristic_table parameters populated. Power flow calculation will raise an error.\n",
    "#  warnings.warn(f\"{trafo_table}: found {mismatch_a} transformer(s) with not both \""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net_diagnostics.trafo.loc[0, \"id_characteristic_table\"] = 0\n",
    "net_diagnostics.trafo.loc[0, \"tap_changer_type\"] = None\n",
    "trafo_characteristic_table_diagnostic(net_diagnostics)\n",
    "#UserWarning: trafo: found 1 transformer(s) with tap_dependency_table set to True and tap_changer_type parameter not populated. The characteristics from trafo_characteristic_table will not be considered.\n",
    " # warnings.warn(f\"{trafo_table}: found {mismatch_b} transformer(s) with tap_dependency_table set to \""
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  },
  "vscode": {
   "interpreter": {
    "hash": "19d1d53a962d236aa061289c2ac16dc8e6d9648c89fe79f459ae9a3493bc67b4"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
